local skynet = require "skynet"
local msghelper = require "tablehelper"
local tabletool = require "tabletool"
local filelog = require "filelog"
local math_random = math.random
local math_ceil = math.ceil
local math_floor = math.floor
local table_insert = table.insert
require "fishenum"
local launcherbase = require "launcher"
local fishlogic = require "fishlogic"
local fishbase = require "fish"
local gameconfig = require "gameconfig"
local fishconfig = gameconfig.fish
local timetrigger = require "timetrigger"
local ratiotool = require "ratiotool"
local fishscript = require "fishscript"
local itemscript = require "itemscript"

local tablelogic
local server
local fishcount = 0
local fishdeadcount = 0
local totalfishcount = 0
local minutecount = 0

local ID = 0
local GROUP_ID = 0
local FRAME_TIME = 0.04
local FISH_TOTAL_WEIGHT = 0
local PAUSE = true
local SCREEN = gameconfig.screen
local MAX_NUM = gameconfig.fishmaxnum
local ALL_LINE_LEN = gameconfig.alllinelen
local DELAY = gameconfig.delay
local WAVING = false
local WAVE_MAX_INDEX = 1

local batchdelayrange = gameconfig.batchdelayrange
local launchers = {}
local fishes, bullets, groups = {}, {}, {}
local fishsizeweightsum = {}
local distribution = {	-- 系数分布
	-- f1, f2, f3, f4, =
}

local GameLogic = {}

local function cal_fish_total_weight()
	local count = 0
	for _, v in ipairs(fishconfig) do
		count = count + v.Weight
		fishsizeweightsum[v.Size] = (fishsizeweightsum[v.Size] or 0) + v.Weight
	end
	FISH_TOTAL_WEIGHT = count
end

function GameLogic.init(tablebase, frame_time)
	server = tablebase
	tablelogic = tablebase.logic.tablelogic
	FRAME_TIME = frame_time
	distribution = server.table_data.distribution or distribution

	cal_fish_total_weight()

	for k in pairs(gameconfig.fishwaves) do
		if k > WAVE_MAX_INDEX then
			WAVE_MAX_INDEX = k
		end
	end

	return true
end

local function remove_dead(dead)
	if type(dead) ~= type{} then return end
	local cnt = 0
	-- filelog.debug("remove dead:", dead)
	for _, id in ipairs(dead[1] or {}) do
		if fishes[id] then
			cnt = cnt + fishes[id].Point
			fishes[id] = nil
			fishdeadcount = fishdeadcount + 1
			fishcount = fishcount - 1
		end
	end
	for _, id in ipairs(dead[2] or {}) do
		if bullets[id] then
			bullets[id]:die()
			bullets[id] = nil
		end
	end
	return cnt
end

local function add_value(laun, val, fishtype)
	tablelogic.changediamond(server.table_data, laun.SeatIndex, val, fishtype)
end

local function del_value(laun, val)
	tablelogic.changediamond(server.table_data, laun.SeatIndex, -val)
end

local function check_combohit(bullet, fish)
	local laun = bullet.Launcher
	if not laun then return end
	if laun.HitEntity then
		local prefish = fishes[laun.HitEntity]
		if prefish and prefish.Stat == EEntityStat.ALIVE then
			if prefish.Id == fish.Id then
				prefish:attacked(bullet)
			else
				prefish:reset_combohit(laun)
				laun:update_hitentity(fish)
				fish:attacked(bullet)
			end
		else
			laun:update_hitentity(fish)
			fish:attacked(bullet)
		end
	else
		laun:update_hitentity(fish)
		fish:attacked(bullet)
	end
end

local function check_hunted(bullet, fish, targets)
	local laun = bullet.Launcher
	if not laun then return end
	if fish.HP > 0 then return end
	local cdiamond, level = tablelogic.get_seat_currdiamond(server.table_data, laun.SeatIndex)
	local val = {
		fish:get_combohit(bullet),
		cdiamond,
		server.table_data.roomdiamond or 0,
		level,
	}
	local f = 1
	for i = 1, 4 do
		-- filelog.warn("f" .. i .. " = ", ratiotool['f' .. i](val[i], distribution[i]), val[i])
		f = f * ratiotool['f' .. i](val[i], distribution[i])
	end
	local ratio = math_ceil(laun.HuntWeight / fish.HuntedWeight * 10000 * f)
	ratio = ratio >= 10000 and 9999 or ratio
	local rand = math_random(10000)
	-- filelog.warn("check_hunted:", rand, ratio)
	if rand <= ratio then
		fish:hunted(bullet)
		fishes[fish.Id] = nil
		fishdeadcount = fishdeadcount + 1
		fishcount = fishcount - 1
		local val = fish.Point
		if fish.Group and fish.Group > 0 then
			val = val + remove_dead{groups[fish.Group]}
			msghelper:sendmsg_to_alltableplayer("s2csyncdelfishesN", {
				deads = deads,
				rid = bullet.Rid,
				dtype = 3,
			})
			groups[fish.Group] = nil
		end

		-- 一定要在fish:hunted()之后
		if type(fishscript[fish.Type]) == "function" then
			local deads, dtype = fishscript[fish.Type](fishes, fish, targets or {})
			if type(deads) == type{} then
				val = val + remove_dead{deads}
				msghelper:sendmsg_to_alltableplayer("s2csyncdelfishesN", {
					deads = deads,
					rid = bullet.Rid,
					dtype = dtype,
				})
			end
		end

		add_value(laun, val * level, fish.Type)

		if fish.DropId then
			itemscript["drop"](bullet.Launcher.SeatIndex, fish.DropId)
		end
	end
end

-- 方案一 碰撞检测
local function collision_detection(bullet, fish)
	local collided = bullet:collision_detection(fish)
	if collided == true then
		check_combohit(bullet, fish)
		check_hunted(bullet, fish)
	end
end

--[[
方案一：
	服务器同步计算实体的位置，每隔一段时间同步服务器数据给客户端，客户端自己做补间，由服务器做碰撞检测；
	此方案可以防止破解作弊，并且数据最准确，还可以排除网络延时的影响，但是服务器压力会相当大！

方案二：（using）
	服务器不实时计算实体的位置和碰撞检测，全部由客户端自己计算，服务器完全信任客户端数据，只做数据转发和计算碰撞效果。
	此方案服务器效率最高，但是防破解作弊效果差，网络延时的影响大！

方案三：
	服务器,客户端各自计算，服务器检测碰撞请求：即客户端发起碰撞请求，服务器判断碰撞是否合法。
	此方案减小了服务器碰撞检测的压力，并且可以不及时计算子弹移动，并且可以防止破解作弊，网络延时影响较小！
	碰撞检测客户端消息携带碰撞时间（10倍毫秒级别），服务器计算碰撞合法性
--]]

-- not use
function GameLogic.run()
	if PAUSE then
		return
	end
	
	local dead = {{}, {}}
	for id, fish in pairs(fishes) do
		if fish.Stat == EEntityStat.ALIVE then
			fish:move(SCREEN)
		elseif fish.Stat == EEntityStat.DEAD then
			table_insert(dead[1], fish.Id)
		end
	end

	for id, bullet in pairs(bullets) do
		if bullet.Stat == EEntityStat.ALIVE then
			bullet:move(SCREEN)
			-- 方案一
			for _, fish in pairs(fishes) do
				if fish.Stat == EEntityStat.ALIVE then
					collision_detection(bullet, fish)	
				end
			end
		elseif bullet.Stat == EEntityStat.DEAD then
			table_insert(dead[2], bullet.Id)
		end
	end

	remove_dead(dead)
end

function GameLogic.flushallfishes()
	filelog.warn("flushallfishes!!!")
	msghelper:sendmsg_to_alltableplayer("flushallfishesN", {})
	fishes = {}
	fishcount = 0
end

function GameLogic.getallfishes()
	return fishes, fishcount
end

-- 方案二 碰撞检测
function GameLogic.collisiondetectioncheck(bulletid, targetid, targetex)
	local bullet = bullets[bulletid]
	local target = fishes[targetid]
	if not bullet or not target then
		if bullet then	-- 如果没有鱼，子弹在客户端已经被移除，这里需要同步移除
			bullet:die()
			bullets[bulletid] = nil
		end
		return false
	end
	if bullet.Stat == EEntityStat.DEAD or target.Stat == EEntityStat.DEAD then
		return false
	end
	check_combohit(bullet, target)
	check_hunted(bullet, target, targetex)
	return true
end

function GameLogic.die(type, id)
	if type == 1 then
		local fish = fishes[id]
		if fish then
			fish:die()
			fishes[id] = nil
			fishdeadcount = fishdeadcount + 1
			fishcount = fishcount - 1
		end
	elseif type == 2 then
		local bullet = bullets[id]
		if bullet then
			bullet:die()
			bullets[id] = nil
		end
	end
end

-- 分钟触发器
local function trigger()
	if PAUSE then return end
	minutecount = minutecount + 1
	filelog.warn('minutecount = ', minutecount)
	if type(timetrigger[0]) == 'function' then 
		timetrigger[0](GameLogic, minutecount)
	end
	if type(timetrigger[minutecount]) == 'function' then 
		timetrigger[minutecount](GameLogic, minutecount)
	end
	skynet.timeout(60*100, trigger)
end

-- 刷鱼触发器
local function fishtrigger()
	if PAUSE then return end
	local rand = math_random(100)
	local groupconfig = gameconfig.fishgroups
	if rand <= groupconfig.percent then
		GameLogic.createfishgroup(math_random(#groupconfig.groups))
	else
		-- GameLogic.createfishbyfunc("createfishbatch", math_random(5, 5))
		GameLogic.createfishbyfunc("createfishbatchbysize")
	end
	local delayrange = batchdelayrange[server.table_data.sitdown_player_num] or batchdelayrange[1]
	skynet.timeout(math_random(delayrange[1], delayrange[2]) * 100, fishtrigger)
end

function GameLogic.gamestart()
	local table_data = server.table_data
	PAUSE = false
	filelog.warn("now game start!")
	-- GameLogic.createfishbyfunc("createfishbatch", math_random(5, 5))
	GameLogic.createfishbyfunc("createfishbatchbysize")

	skynet.timeout(60 * 100, trigger)
	skynet.timeout(15 * 100, fishtrigger)
end

function GameLogic.onegameend()
	local table_data = server.table_data
	table_data.gaming = false

	PAUSE = true

	fishcount = 0
	fishdeadcount = 0
	fishes = {}
	bullets = {}
	launchers = {}
end

function GameLogic.createlauncher(rid, seat_index, type, level)
	local config = tabletool.deepcopy(gameconfig.launcher[type][level])
	if not config then
		filelog.error("createlauncher failed: no config!", type, level)
		return
	end
	config.Id = rid
	config.Rid = rid
	config.SeatIndex = seat_index
	config.Level = level or config.Level
	launchers[rid] = launcherbase:new(msghelper.s2csynclauncher, msghelper.s2csyncexlauncher, config)
	return launchers[rid]
end

function GameLogic.upgradelauncher(rid, seat_index, laun, level)
	local config = tabletool.deepcopy(gameconfig.launcher[laun.Type][level])
	if not config then
		filelog.error("upgradelauncher failed: no config!", laun.Type, level)
		return
	end
	config.Id = rid
	config.Rid = rid
	config.SeatIndex = seat_index
	config.Level = level
	laun:upgrade(config)
end

function GameLogic.destroylauncher(rid)
	if launchers[rid] then
		launchers[rid]:die()
		launchers[rid] = nil
	end
end

function GameLogic.createfish(c, num, delay)
	local config
	if type(c) == type{} then
		config = tabletool.deepcopy(c)
	else
		config = tabletool.deepcopy(gameconfig.fish[c])
	end
	if not config then
		filelog.error("create fish failed: no config!")
		return
	end
	local n = num or config.Num[math_random(#config.Num)]
	local llen = #config.Lines
	llen = llen > 0 and llen or (ALL_LINE_LEN - n)
	local lpre = math_random(llen) - 1
	-- local fishset = {}
	for i = 1, n do
		if WAVING then
			return
		end
		ID = ID + 1
		config.Id = ID
		config.Ts = skynet.time()
		lpre = lpre + 1
		config.PosIndex = config.Lines[lpre % llen + 1]	-- 避免重叠
		-- config.Positions = tabletool.deepcopy(gameconfig.positions[config.PosIndex])
		-- config.Speed = config.Speed*FRAME_TIME
		local fish = fishbase:new(msghelper.s2csyncfish, config)
		fishes[fish.Id] = fish
		fishcount = fishcount + 1
		totalfishcount = totalfishcount + 1
		-- table_insert(fishset, fish)
		skynet.sleep((delay or DELAY)*100)
	end
	-- return fishset
end

function GameLogic.createfishbatchbysize(sizes)
	local table_data = server.table_data
	sizes = sizes or gameconfig.fishsizes[table_data.sitdown_player_num]
	if fishcount >= MAX_NUM then return end
	if WAVING then return end
	local sizesets = {}
	local bigsizenum = 0
	if type(sizes) ~= type{} then
		return
	else
		for size, v in ipairs(sizes) do
			if size == 3 then bigsizenum = v end
			for i = 1, v do
				table_insert(sizesets, size)
			end  
		end
	end

	for i = 1, #sizesets - bigsizenum do
		local r = math_random(1, #sizesets + 1 - i - bigsizenum)
		sizesets[r], sizesets[#sizesets + 1 - i - bigsizenum] = sizesets[#sizesets + 1 - i - bigsizenum], sizesets[r]
	end
	-- filelog.debug("createfishbatchbysize sizesets = ", sizesets)
	-- local createdfishes = {}
	local starttime = skynet.time()
	for _, size in ipairs(sizesets) do
		if WAVING then
			return
		end
		local rand = math_random(fishsizeweightsum[size])
		for t, c in ipairs(fishconfig) do
			if c.Size == size then
				rand = rand - c.Weight
				if rand <= 0 then
					GameLogic.createfish(c)
					-- for _, v in ipairs(GameLogic.createfish(c)) do
					-- 	table_insert(createdfishes, msghelper:copy_fishinfo({}, v))
					-- end
					break
				end
			end
		end
	end
	-- msghelper:sendmsg_to_alltableplayer("s2csyncaddfishesN", {
	-- 	fishes = createdfishes,
	-- 	delay = 1,
	-- })

	filelog.debug("createfishbatch by size over, now fishcount = ", fishcount, ",use time = ", skynet.time() - starttime)
end

function GameLogic.createfishbatch(num)
	if fishcount >= MAX_NUM then return end
	num = num or 5
	-- local createdfishes = {}
	for i = 1, num do
		if WAVING then
			return
		end
		local rand = math_random(FISH_TOTAL_WEIGHT)
		for t, c in ipairs(fishconfig) do
			rand = rand - c.Weight
			if rand <= 0 then
				GameLogic.createfish(c)
				-- for _, v in ipairs(GameLogic.createfish(c)) do
				-- 	table_insert(createdfishes, msghelper:copy_fishinfo({}, v))
				-- end
				break
			end
		end
	end
	-- msghelper:sendmsg_to_alltableplayer("s2csyncaddfishesN", {
	-- 	fishes = createdfishes,
	-- 	delay = 1,
	-- })
end

local function createfishgroupbase(c, fishdelay, isgroup)
	if not c then return {} end
	local num = c.Num
	local config = tabletool.deepcopy(fishconfig[c.Type])
	local fishset = {}
	if config then
		config.Lines = c.Lines or config.Lines
		config.OffsetX = c.OffsetX
		config.OffsetY = c.OffsetY
		if isgroup then
			GROUP_ID = GROUP_ID + 1
			config.Group = GROUP_ID
		end
		for i = 1, num do
			ID = ID + 1
			config.Id = ID
			config.Ts = skynet.time()
			config.PosIndex = config.Lines[math_random(#config.Lines)]
			local fish = fishbase:new(msghelper.s2csyncfish, config, true)
			-- local fish = fishbase:new(msghelper.s2csyncfish, config)
			fishes[fish.Id] = fish
			fishcount = fishcount + 1
			totalfishcount = totalfishcount + 1
			if config.Group then
				groups[GROUP_ID] = groups[GROUP_ID] or {}
				table_insert(groups[GROUP_ID], ID)
			end
			if fishdelay then
				skynet.sleep(fishdelay * 100)
			end
			table_insert(fishset, fish)
		end
	end
	return fishset
end

function GameLogic.createfishgroup(idx)
	local groupconfig = gameconfig.fishgroups.groups[idx]
	if not groupconfig then
		return
	end
	local fishes = groupconfig.fishes
	local fishdelay = groupconfig.fishdelay
	-- local createdfishes = {}
	for _, c in ipairs(fishes) do
		if WAVING then
			return
		end
		createfishgroupbase(c, fishdelay)
		-- for _, v in ipairs(createfishgroupbase(c, nil, true)) do
		-- 	table_insert(createdfishes, msghelper:copy_fishinfo({}, v))
		-- end
	end
	-- msghelper:sendmsg_to_alltableplayer("s2csyncaddfishesN", {
	-- 	fishes = createdfishes,
	-- 	delay = 0,
	-- })
end

function GameLogic.createfishlinked(idx)
	local linkconfig = gameconfig.fishlinked[idx]
	if not linkconfig then
		return
	end
	local fishes = linkconfig.fishes
	local createdfishes = {}
	for _, c in ipairs(fishes) do
		if WAVING then
			return
		end
		for _, v in ipairs(createfishgroupbase(c)) do
			table_insert(createdfishes, msghelper:copy_fishinfo({}, v))
		end
	end
	msghelper:sendmsg_to_alltableplayer("s2csyncaddfishesN", {
		fishes = createdfishes,
		delay = 0,
	})
end

function GameLogic.createfishwave(idx)
	local waveconfig = gameconfig.fishwaves[idx]
	if not waveconfig then
		idx = idx % WAVE_MAX_INDEX + 1
		waveconfig = gameconfig.fishwaves[idx]
		if not waveconfig then
			filelog.error("create fish wave failed: no config!", idx, WAVE_MAX_INDEX)
			return
		end
	end

	WAVING = true
	GameLogic.flushallfishes()
	skynet.timeout(waveconfig.wavetimelen * 100, function()
		WAVING = false
		filelog.warn("fish wave over!")
	end)

	for i, wave in ipairs(waveconfig.waves) do
		-- local createdfishes = {}
		for _, fwave in ipairs(wave.fishes) do
			local createdfishes = {}
			-- skynet.fork(function()
				if PAUSE then return end
				for _, c in ipairs(fwave) do
					-- createfishgroupbase(c, wave.fishdelay)
					for _, v in ipairs(createfishgroupbase(c)) do
						table_insert(createdfishes, msghelper:copy_fishinfo({}, v))
					end
				end
			-- end)

			msghelper:sendmsg_to_alltableplayer("s2csyncaddfishesN", {
				fishes = createdfishes,
				delay = 0,
			})
			skynet.sleep(wave.fishdelay * 100)
		end
		-- msghelper:sendmsg_to_alltableplayer("s2csyncaddfishesN", {
		-- 	fishes = createdfishes,
		-- 	delay = 0,
		-- })
		filelog.warn("createfishwave:", idx, i)
		skynet.sleep(waveconfig.wavedelay * 100)
	end
end

function GameLogic.createfishrand(sizes)
	if fishcount >= MAX_NUM then return end
	local posx, posy, angle, config, size
	local posindex = -1	-- -1 用作无配置轨迹的对象，游出屏幕即销毁
	local table_data = server.table_data
	sizes = sizes or gameconfig.fishsizes[table_data.sitdown_player_num]
	local n = 0
	if type(sizes) ~= type{} then
		sizes = {}
	else
		for _, v in ipairs(sizes) do
			n = n + v
		end
	end
	for i = 1, n do
		if i <= (sizes[1] or 8) then	-- 小鱼
			size = 1
		elseif i > (sizes[1] or 8) and i <= (sizes[1] or 8) + (sizes[2] or 5) then	-- 中鱼
			size = 2
		else	-- 大鱼
			size = 3
		end
		local rand = math_random(fishsizeweightsum[size])
		for t, c in ipairs(fishconfig) do
			if c.Size == size then
				rand = rand - c.Weight
				if rand <= 0 then
					config = tabletool.deepcopy(c)
					break
				end
			end
		end

		local pos = math_random(4)
		if pos == 1 then 		-- 左外屏
			-- 先随机一个起点
			posx = math_random(-80, -50)
			posy = math_random(200, 980)
			-- 再随机一个角度
			if math_random(2) == 1 then
				angle = math_random(20, 90)
			else
				angle = math_random(90, 160)
			end
		elseif pos == 2 then 	-- 上外屏
			-- 先随机一个起点
			posx = math_random(200, 1720)
			posy = math_random(1130, 1160)
			-- 再随机一个角度
			if math_random(2) == 1 then
				angle = math_random(110, 180)
			else
				angle = math_random(180, 250)
			end
		elseif pos == 3 then	-- 右外屏
			-- 先随机一个起点
			posx = math_random(1970, 2000)
			posy = math_random(200, 980)
			-- 再随机一个角度
			if math_random(2) == 1 then
				angle = math_random(200, 270)
			else
				angle = math_random(270, 340)
			end
		else 					-- 下外屏
			-- 先随机一个起点
			posx = math_random(200, 1720)
			posy = math_random(-80, -50)
			-- 再随机一个角度
			if math_random(2) == 1 then
				angle = math_random(0, 70)
			else
				angle = math_random(290, 360)
			end
		end

		config.PosIndex = posindex
		config.PosX = posx
		config.PosY = posy
		config.Angle = angle
		GameLogic.createfish(config, 1)
	end
end

function GameLogic.createfishbyfunc(fname, ...)
	if GameLogic[fname] then
		skynet.fork(GameLogic[fname], ...)
	end
end

-- 最好由客户端计算角度
-- function GameLogic.shoot(tap_x, tap_y)
function GameLogic.shoot(rid, id, angle, level, target)
	local laun = launchers[rid]
	if laun then
		-- laun:turn{tap_x, tap_y}
		laun:turn(angle)
		if target and target > 0 then
			laun:set_target(target)
		else
			laun:set_target()
		end
		local ts = skynet.time()
		if laun:checkcan_shoot(ts) then	-- 冷却时间
			local config = tabletool.deepcopy(gameconfig.bullet[laun.Type][laun.Level])
			if not config then
				filelog.error("createbullet failed: no config!", laun.Type, laun.Level)
				return
			end
			config.Id = id
			config.Rid = rid
			config.Target = target
			-- config.Speed = math_floor(config.Speed*FRAME_TIME)
			local bullet = laun:shoot(msghelper.s2csyncbullet, msghelper.s2csyncexbullet, config, ts)
			if bullet then
				bullets[bullet.Id] = bullet
				del_value(laun, laun.Point * level)
			end
		else
			filelog.warn("shoot failed: cding!", ts - laun.CDing)
		end
	end
end

-- 修改捕鱼系数分布
function GameLogic.updatedistribution(data)
	if type(data) ~= type{} then return end
	for i, v in ipairs(data) do
		distribution[i] = data[i]
	end
end

return GameLogic